﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Diagnostics;
using SimpleIR;

namespace InterpreterCore
{
	/// <summary>
	/// This struct represents the evaluator stack entries
	/// </summary>
	internal struct EvalStackEntry
	{
		/// <summary>
		/// The type of the eval stack location.
		/// </summary>
		public readonly IRType EntryType;

		/// <summary>
		/// Value if the type is a number.
		/// </summary>
		private readonly StackNumericValue m_numval;

		/// <summary>
		/// Value if the type is a pointer.
		/// </summary>
		private readonly PtrValue m_ptrval;

		/// <summary>
		/// Value if the type is a pointer.
		/// </summary>
		private readonly ReferenceValue m_refval;

		/// <summary>
		/// Value if the type is a struct or a function pointer then this is a pointer to the object 
		/// that represent it.
		/// </summary>
		private readonly object m_structorfpval;

		/////////////////////////////////////////////////
		//Internal constructors 

		private EvalStackEntry(StackNumericValue nv, IREvalStackPrimitiveType evst)
		{
			this.EntryType = evst;

			this.m_numval = nv;
			this.m_ptrval = PtrValue.MakeNullPtr(null);
			this.m_refval = ReferenceValue.MakeNullReference(null);
			this.m_structorfpval = null;
		}

		private EvalStackEntry(PtrValue pv)
		{
			this.EntryType = pv.ValType;

			this.m_numval = StackNumericValue.MakeZeroStackValue();
			this.m_ptrval = pv;
			this.m_refval = ReferenceValue.MakeNullReference(null);
			this.m_structorfpval = null;
		}

		private EvalStackEntry(ReferenceValue rv)
		{
			this.EntryType = rv.ValType;

			this.m_numval = StackNumericValue.MakeZeroStackValue();
			this.m_ptrval = PtrValue.MakeNullPtr(null);
			this.m_refval = rv;
			this.m_structorfpval = null;
		}

		private EvalStackEntry(CompositeValueEntry[] cv, IRStructType evst)
		{
			this.EntryType = evst;

			this.m_numval = StackNumericValue.MakeZeroStackValue();
			this.m_ptrval = PtrValue.MakeNullPtr(null);
			this.m_refval = ReferenceValue.MakeNullReference(null);
			this.m_structorfpval = cv;
		}

		private EvalStackEntry(IRInvokeIdentity iiv, IRFunctionPointerType evst)
		{
			this.EntryType = evst;

			this.m_numval = StackNumericValue.MakeZeroStackValue();
			this.m_ptrval = PtrValue.MakeNullPtr(null);
			this.m_refval = ReferenceValue.MakeNullReference(null);
			this.m_structorfpval = iiv;
		}

		internal IEnumerable<int> EnumerateTrgtAddresses(IRType entrytype)
		{
			if(entrytype is IRPtrType)
				yield return this.m_ptrval.TargetAddress;

			if(entrytype is IRReferenceType)
				yield return this.m_refval.TargetAddress;

			if(entrytype is IRStructType)
			{
				CompositeValueEntry[] cvee = (CompositeValueEntry[])this.m_structorfpval;
				for(int j = 0; j < cvee.Length; ++j)
				{
					if(cvee[j].Offset.PathLast.FieldType is IRPtrType)
					{
						PtrValue cpv = cvee[j].Value.ConvertToPassablePtrValue();
						yield return cpv.TargetAddress;
					}
				}
			}
		}

		internal EvalStackEntry ConvertToType(IRType tt)
		{
			if(tt is IRPrimitiveNumericType || tt is IREnumType)
			{
				if(tt is IREnumType)
					tt = ((IREnumType)tt).UnerlyingNumericType;

				NumericValue nv = this.ReadNumericValueAsType((IRPrimitiveNumericType)tt);
				IREvalStackPrimitiveType ivept = (IREvalStackPrimitiveType)GlobalInterpreterState.TProg.ConvertTypeToStdStackRep(tt);
				return EvalStackEntry.CreateNumericEvalEntry(nv, ivept);
			}
			else if(tt is IRPtrType)
			{
				PtrValue npv = new PtrValue(this.m_ptrval.TargetAddress, (IRPtrType)tt);
				return new EvalStackEntry(npv);
			}
			else
			{
				Debug.Assert(this.EntryType == tt);

				return this;
			}
		}

		public override string ToString()
		{
			if(this.EntryType is IREvalStackPrimitiveType)
				return this.m_numval.ToString();
			else if(this.EntryType is IRPtrType)
				return this.m_ptrval.ToString();
			else if(this.EntryType is IRReferenceType)
				return this.m_refval.ToString();
			else if(this.EntryType is IRFunctionPointerType)
				return ((IRInvokeIdentity)this.m_structorfpval).ToString();
			else
				return CompositeValue.Stringify((CompositeValueEntry[])this.m_structorfpval);
		}

		/// <summary>
		/// The value formatted as a string for display in the debugger.
		/// </summary>
		internal string DebuggerString()
		{
			if(this.EntryType is IREvalStackPrimitiveType)
				return this.m_numval.DebuggerString(this.EntryType);
			else if(this.EntryType is IRPtrType)
				return this.m_ptrval.DebuggerString();
			else if(this.EntryType is IRReferenceType)
				return this.m_refval.DebuggerString();
			else if(this.EntryType is IRFunctionPointerType)
				return ((IRInvokeIdentity)this.m_structorfpval).ToString();
			else
				return CompositeValue.DebuggerString((CompositeValueEntry[])this.m_structorfpval);
		}

		/// <summary>
		/// The value formatted as a string in a format for emitting in the trace.
		/// </summary>
		internal string EmitForTrace()
		{
			string valstr;
			if(this.EntryType is IREvalStackPrimitiveType)
				valstr = String.Format("value={0}", this.m_numval.EmitForTrace(this.EntryType));
			else if(this.EntryType is IRPtrType)
				valstr = String.Format("value={0}", this.m_ptrval.EmitForTrace());
			else if(this.EntryType is IRReferenceType)
				valstr = String.Format("value={0}", this.m_refval.EmitForTrace());
			else if(this.EntryType is IRFunctionPointerType)
				valstr = String.Format("value={0} funcname={1}::{2}", ((IRInvokeIdentity)this.m_structorfpval).InvIDStableKey, ((IRInvokeIdentity)this.m_structorfpval).DefInClass.BaseTypeName, ((IRInvokeIdentity)this.m_structorfpval).InvSig.InvokeName);
			else
				valstr = String.Format("value={0}", CompositeValue.EmitForTrace((CompositeValueEntry[])this.m_structorfpval));

			return String.Format("type={0} typename={1} {2}", this.EntryType.TypeID, this.EntryType.BaseTypeName, valstr);
		}

		/// <summary>
		/// The value formatted and output for a heap dump.
		/// </summary>
		internal void SerializeForHeapDump(System.IO.TextWriter dumpstr)
		{
			string typestr = String.Format("type={0} value=", this.EntryType.TypeID);

			if(this.EntryType is IREvalStackPrimitiveType)
				this.m_numval.SerializeForHeapDump(dumpstr, this.EntryType);
			else if(this.EntryType is IRPtrType)
				this.m_ptrval.SerializeForHeapDump(dumpstr);
			else if(this.EntryType is IRReferenceType)
				this.m_refval.SerializeForHeapDump(dumpstr);
			else if(this.EntryType is IRFunctionPointerType)
				dumpstr.Write(((IRInvokeIdentity)this.m_structorfpval).InvIDStableKey);
			else
				CompositeValue.SerializeForHeapDump(dumpstr, (CompositeValueEntry[])this.m_structorfpval);
		}

		/// <summary>
		/// Serialize the location for DGML display
		/// </summary>
		internal void SerializeForDGMLDisplay(System.Xml.XmlWriter nodexw, System.Xml.XmlWriter edgexw, string stackname)
		{
			if(this.EntryType is IRPtrType || this.EntryType is IRReferenceType)
			{
				nodexw.WriteStartElement("Node");
				nodexw.WriteAttributeString("Id", stackname);
				nodexw.WriteAttributeString("Label", stackname);
				nodexw.WriteAttributeString("Background", "gold");
				nodexw.WriteEndElement();

				int trgt;
				if(this.EntryType is IRReferenceType)
					trgt = this.m_refval.IsNullReference ? this.m_refval.TargetAddress : -1;
				else
					trgt = this.m_ptrval.IsNullPtr ? this.m_ptrval.TargetAddress : -1;

				if(trgt != -1)
				{
					edgexw.WriteStartElement("Link");
					edgexw.WriteAttributeString("Source", stackname);
					edgexw.WriteAttributeString("Target", trgt.ToString());
					edgexw.WriteEndElement();
				}
			}
			else if(this.EntryType is IRStructType)
			{
				if(((IRStructType)this.EntryType).TypeDescription.AllTerminalAccessPaths.Any((IRBasicAccessPath bap) => bap.PathLast.FieldType is IRPtrType))
				{
					nodexw.WriteStartElement("Node");
					nodexw.WriteAttributeString("Id", stackname);
					nodexw.WriteAttributeString("Label", stackname);
					nodexw.WriteAttributeString("Background", "gold");
					nodexw.WriteEndElement();

					CompositeValueEntry[] cve = (CompositeValueEntry[])this.m_structorfpval;
					for(int i = 0; i < cve.Length; ++i)
					{
						if(cve[i].Offset.PathLast.FieldType is IRPtrType)
						{
							PtrValue pv = cve[i].Value.ConvertToPassablePtrValue();
							if(!pv.IsNullPtr)
							{
								edgexw.WriteStartElement("Link");
								edgexw.WriteAttributeString("Source", stackname);
								edgexw.WriteAttributeString("Target", pv.TargetAddress.ToString());
								edgexw.WriteAttributeString("Label", cve[i].Offset.FieldOnlyDisplayString());
								edgexw.WriteEndElement();
							}
						}
					}
				}
			}
			else
			{
				;
			}
		}

		/// <summary>
		/// Parse a numeric value of the given type from a string.
		/// </summary>
		internal static EvalStackEntry ParseValue(string val)
		{
			Dictionary<string, string> kvpd = TracingSystem.ParseKeyValuePairs(val);

			string typeidstr = kvpd["type"];
			int typeid = Int32.Parse(typeidstr);
			IRType evstype = GlobalInterpreterState.TProg.TypeTable.LookupTypeForID(typeid);

			string valstr = kvpd["value"];
			if(evstype is IREvalStackPrimitiveType)
			{
				StackNumericValue snv = StackNumericValue.ParseValue((IREvalStackPrimitiveType)evstype, valstr);
				return new EvalStackEntry(snv, (IREvalStackPrimitiveType)evstype);
			}
			else if(evstype is IRPtrType)
			{
				PtrValue pv = PtrValue.ParseValue((IRPtrType)evstype, valstr);
				return new EvalStackEntry(pv);
			}
			else if(evstype is IRReferenceType)
			{
				ReferenceValue rv = ReferenceValue.ParseValue((IRReferenceType)evstype, valstr);
				return new EvalStackEntry(rv);
			}
			else if(evstype is IRFunctionPointerType)
			{
				int invid = Int32.Parse(valstr);
				IRInvokeIdentity iiv = GlobalInterpreterState.TProg.InvokeTable.GetInvokeWithID(invid);
				return new EvalStackEntry(iiv, (IRFunctionPointerType)evstype);
			}
			else
			{
				CompositeValueEntry[] cve = CompositeValue.ParseValue((IRStructType)evstype, valstr);
				return new EvalStackEntry(cve, (IRStructType)evstype);
			}
		}

		/////////////////////////////////////////////////
		//These methods are used for creating a value when pushing it on the eval stack

		internal static EvalStackEntry CreateNumericEvalEntry(NumericValue nv, IREvalStackPrimitiveType evst)
		{
			StackNumericValue snv = StackNumericValue.MakeFromNumericValue(nv);
			return new EvalStackEntry(snv, evst);
		}

		internal static EvalStackEntry CreateRawStackNumeric(StackNumericValue snv, IREvalStackPrimitiveType evst)
		{ return new EvalStackEntry(snv, evst); }

		internal static EvalStackEntry CreatePointerEvalEntry(PtrValue pv)
		{ return new EvalStackEntry(pv); }

		internal static EvalStackEntry CreateReferenceEvalEntry(ReferenceValue rv)
		{ return new EvalStackEntry(rv); }

		internal static EvalStackEntry CreateStructEvalEntry(CompositeValueEntry[] cv, IRStructType evst)
		{ return new EvalStackEntry(cv, evst); }

		internal static EvalStackEntry CreateFunctionPtrEvalEntry(IRInvokeIdentity iiv, IRFunctionPointerType evst)
		{ return new EvalStackEntry(iiv, evst); }

		/////////////////////////////////////////////////
		//These methods are used for extracting a value when popping it off the eval stack

		internal NumericValue ReadNumericValueAsType(IRPrimitiveNumericType pnt)
		{
			Debug.Assert(this.EntryType is IREvalStackPrimitiveType);
			
			return this.m_numval.ConvertToNumericValue_Basic(pnt, (IREvalStackPrimitiveType)this.EntryType);
		}

		internal StackNumericValue ReadRawStackNumeric()
		{ return this.m_numval; }

		internal PtrValue ReadPointerAsType(IRPtrType pt)
		{
			Debug.Assert(GlobalInterpreterState.TProg.IsT1AssignableToT2(this.EntryType, pt));

			return new PtrValue(this.m_ptrval.TargetAddress, pt); 
		}

		internal PtrValue ReadPointerNoTypeConvert()
		{
			Debug.Assert(this.EntryType is IRPtrType);

			return this.m_ptrval; 
		}

		internal ReferenceValue ReadReference()
		{
			Debug.Assert(this.EntryType is IRReferenceType);

			return this.m_refval;
		}

		internal CompositeValueEntry[] ReadStruct()
		{
			Debug.Assert(this.EntryType is IRStructType);

			return (CompositeValueEntry[])this.m_structorfpval; 
		}

		internal IRInvokeIdentity ReadFunctionPtr()
		{
			Debug.Assert(this.EntryType is IRFunctionPointerType);

			return (IRInvokeIdentity)this.m_structorfpval; 
		}
	}
}
